<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- Object.is() - 相等判定 -->
    <script>
        console.log(Object.is(-0, +0), -0 === +0); // false true
        // 利用递归判定多个值
        function recursivelyCheckEqual(x, ...rest) {
            return Object.is(x, rest[0]) && ((rest.length < 2) || recursivelyCheckEqual(...rest));
        }
        console.log(recursivelyCheckEqual(1, 1, 1, 2));
    </script>

    <!-- Object.assign() - 浅拷贝对象, 对象的合并 -->
    <script>
        const a1 = { a1: 1 }, a2 = { a2: 2 }, a3 = { a3: 3 };
        Object.assign(a1, a2, a3);
        console.log(a1);
        console.log(Object.assign(2) instanceof Object); // 把2转换为Number(2)
        // 浅拷贝 - 拷贝对象的引用（值同时变化）
        const obj1 = { a: { b: 1 } };
        const obj2 = Object.assign({}, obj1);
        obj1.a.b = 2;
        console.log(obj2.a.b);
        // 处理数组 - 转换为属性名为下标的对象, 同名替换
        console.log(Object.assign([1, 2, 3], [4, 5]));  // [4, 5, 3]
        // 处理取值函数 - 求值后复制
        const source = { get foo() { return 1 } };
        console.log(Object.assign({}, source)); // foo: 1
        // 用途1 - 给对象添加属性
        class Point {
            constructor(x, y) {
                Object.assign(this, { x, y });
            } // 将x属性和y属性添加到Point类的对象实例
        }
        // 用途2 - 批量给对象添加方法
        function SomeClass() { }
        Object.assign(SomeClass.prototype, {
            someMethod(arg1, arg2) { },
            anotherMethod() { }
        }); // === SomeClass.prototype.someMethod = function(arg1, arg2) { }
        // 用途3 - 克隆对象（保持继承链克隆继承值）
        function clone(origin) {
            let originProto = Object.getPrototypeOf(origin);
            return Object.assign(Object.create(originProto), origin);
        }
        // 用途4 - 合并多个对象, 返回一个新对象
        let merge = (...res) => Object.assign({}, ...res);
        // 利用合并对象赋默认值
        const DEFAULTS = { logLevel: 0, outputFormat: 'html' };
        function processContent(options) {
            options = Object.assign({}, DEFAULTS, options);
            console.log(options);
        }
    </script>

    <!-- Object.getOwnPropertyDescriptors() - 返回指定对象所有自身属性（非继承属性）的描述对象 -->
    <script>
        // 用途1 - 解决Object.assign()无法正确拷贝get和set的问题
        const shallowMerge = (target, source) => Object.defineProperties(
            target,
            Object.getOwnPropertyDescriptors(source)
        );
        // 用途2 - 配合Object.create()方法，将对象属性克隆到一个新对象。这属于浅拷贝
        {
            const obj = { a: 1, b: 2 };
            const clone = Object.create(Object.getPrototypeOf(obj),
                Object.getOwnPropertyDescriptors(obj));
        }
        // 用途3 - 替代__proto__实现对象继承
        const prot = {};
        const objProto1 = {
            __proto__: prot,
            foo: 123,
        };
        const objProto2 = Object.create(
            prot, Object.getOwnPropertyDescriptors({
                foo: 123
            })
        );
    </script>

    <!-- __proto__ - 实例对象的原型对象 -->
    <script>
        // 隐式原型
        function Obj() { }
        let obj = new Obj();
        console.log(obj.__proto__ === Obj.prototype);
    </script>

    <!-- Object.setPrototypeOf() - 设置一个对象的原型对象 -->
    <!-- Object.getPrototypeOf() - 读取一个对象的原型对象 -->
    <script>
        {
            //  Object.setPrototypeOf()
            // 作用与__proto__相同
            let obj1 = { a: 1, b: 1 };
            let obj2 = { c: 1 };
            obj2.__proto__ = obj1;
            console.log(obj2.a);

            let obj3 = { a: 1, b: 1 };
            let obj4 = Object.setPrototypeOf({ c: 1 }, obj3);
            console.log(obj4, obj4.a);

            //  Object.getPrototypeOf()
            console.log(Object.getPrototypeOf(obj4) === obj3);
        }
    </script>

    <!-- Object.keys(), Object.values(), Object.entries(), Object.fromEntries() -->
    <script>
        // 只能遍历出属性描述对象的enumerable是true的属性
        // 属性名为数值时，按照数值大小从小到大遍历
        const enumerable = { 1: 'a', 3: 'c', 2: 'b', a: '1', c: '3', b: '2', d: 'd' };
        // Object.keys()
        console.log(Object.keys(enumerable));
        for (let key of Object.keys(enumerable)) {
            console.log(key);
        }
        // Object.values()
        // 参数不是对象时将其转换成对象, 过滤属性名为Symbol值的属性
        console.log(Object.values('enumerable'));
        console.log(Object.values(enumerable));
        for (let value of Object.values(enumerable)) {
            console.log(value);
        }
        // Object.entries()
        // 过滤属性名为Symbol值的属性
        console.log(Object.entries(enumerable));
        for (let entrie of Object.entries(enumerable)) {
            console.log(entrie);
        }
        // 可以将对象转为真正的Map结构
        {
            const obj = { foo: 'bar', baz: 42 };
            const map = new Map(Object.entries(obj));
            console.log(map);
        }
        // Object.fromEntries()
        // .entries()的逆操作, 将一个键值对数组（Map结构）转为对象
        const entries = new Map([['foo', 'bar'], ['baz', 42]]);
        console.log(Object.fromEntries(entries));
    </script>
</body>

</html>